/* Copyright (c) 2008 Google Inc.
*  Copyright (c) 2009 Pierre Henri Kuate.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
namespace org.opensocial.data
{
    /// <summary> An object representing a generic, extensible OpenSocial entity. Virtually
    /// every object, both concrete (person) or abstract (data), having arbitrary
    /// properties is modeled as an OpenSocialObject instance. Instance methods
    /// provide an interface for associating strings with objects representing
    /// properties or attributes of that object. These fields can in turn
    /// reference other OpenSocialObject instances.
    /// </summary>
    /// <author>  Jason Cooper
    /// </author>
    public class OpenSocialObject
    {
        protected System.Collections.Generic.IDictionary<string, OpenSocialField> fields =
            new System.Collections.Generic.Dictionary<string, OpenSocialField>();

        public OpenSocialObject()
        {
        }

        /// <summary> Instantiates a new OpenSocialObject object with the passed Map of
        /// OpenSocialField objects keyed on strings, replicating these
        /// correspondences in its own fields Map.
        /// </summary>
        /// <param name="properties">Map of OpenSocialField objects keyed on field name
        /// which should be "imported" upon instantiation
        /// </param>
        public OpenSocialObject(System.Collections.Generic.IDictionary<string, OpenSocialField> properties)
        {
            foreach (var property in properties)
                setField(property.Key, property.Value);
        }

        /// <summary> Returns {@code true} if a field with the passed key is associated with
        /// the current instance, {@code false} otherwise.
        /// </summary>
        public bool hasField(string key)
        {
            return fields.ContainsKey(key);
        }

        /// <summary> Returns field mapped to the passed key.
        /// </summary>
        /// <param name="key">Key associated with desired field
        /// </param>
        public OpenSocialField getField(string key)
        {
            OpenSocialField value;
            return fields.TryGetValue(key, out value) ? value : null;
        }

        /// <summary> Generic method which takes the name of the string field and returns its value
        /// </summary>
        public string getStringValue(string fieldName)
        {
            OpenSocialField field = getField(fieldName);

            return field != null ? field.StringValue : null;
        }

        public int? getIntegerValue(string key)
        {
            OpenSocialField field = getField(key);

            if (field != null)
            {
                int result;
                return int.TryParse(field.StringValue, out result) ? (int?)result : null;
            }

            return null;
        }

        public long? getLongValue(string key)
        {
            OpenSocialField field = getField(key);

            if (field != null)
            {
                long result;
                return long.TryParse(field.StringValue, out result) ? (long?)result : null;
            }

            return null;
        }

        public double? getDoubleValue(string key)
        {
            OpenSocialField field = getField(key);

            if (field != null)
            {
                double result;
                return double.TryParse(field.StringValue,
                        System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture,
                        out result)
                    ? (double?)result : null;
            }

            return null;
        }

        public bool? getBoolValue(string key)
        {
            OpenSocialField field = getField(key);

            if (field != null)
            {
                bool result;
                return bool.TryParse(field.StringValue, out result) ? (bool?)result : null;
            }

            return null;
        }

        public System.DateTime? getDateTimeValue(string key)
        {
            OpenSocialField field = getField(key);

            if (field != null)
            {
                System.DateTime result;
                return System.DateTime.TryParse(field.StringValue, out result) ? (System.DateTime?)result : null; // TODO: Is the value parsable by DateTime?
            }

            return null;
        }

        public System.Collections.Generic.IList<T> getObjects<T>(string key)
            where T : OpenSocialObject, new()
        {
            OpenSocialField field = getField(key);

            if (field != null)
            {
                var objects = new System.Collections.Generic.List<T>();
                foreach (var value in field.getValues())
                {
                    var o = new T();
                    objects.Add(o);
                    o.fields = value.fields;
                }
                return objects;
            }

            return null;
        }

        public T singleOrDefaultObjectOfCollection<T>(string key)
            where T : OpenSocialObject, new()
        {
            var list = getObjects<T>(key);
            if (list != null && list.Count > 0) // TODO: Check that list.Count == 1? (If more, throw exception like Linq's SingleOrDefault()
                return list[0];
            return null;
        }

        public MediaItemType? getMediaType(string key)
        {
            OpenSocialField field = getField(key);

            if (field != null)
            {
                var type = field.StringValue;
                if (type.Equals("audio", System.StringComparison.InvariantCultureIgnoreCase))
                    return MediaItemType.AUDIO;
                if (type.Equals("image", System.StringComparison.InvariantCultureIgnoreCase))
                    return MediaItemType.IMAGE;
                if (type.Equals("video", System.StringComparison.InvariantCultureIgnoreCase))
                    return MediaItemType.VIDEO;
                throw new OpenSocialException("Unknown MediaItemType: " + type);
            }

            return null;
        }

        public Person getPerson(string key)
        {
            OpenSocialObject oso = getField(key).getValues()[0];
            string[] field_names = oso.fieldNames();
            
            Person person = new Person();
            foreach (string field_name in field_names)
            {
                person.setField(field_name, oso.getField(field_name));
            }

            return person;
        }

        public IList<string> getStringValues(string key)
        {
            OpenSocialField field = getField(key);

            if (field != null)
            {
                return field.getStringValues();
            }

            return null;
        }

        public IList<string[]> getStringArrayValues(string key)
        {
            OpenSocialField field = getField(key);

            if (field != null)
            {
                return field.getStringArrayValues();
            }

            return null;
        }

        public Dictionary<string, string> getStringDictionaryValues(string key)
        {
            OpenSocialField field = getField(key);

            if (field != null)
            {
                return field.getStringDictionaryValues();
            }

            return null;
        }




        /// <summary> Creates a new entry in fields Map, associating the passed OpenSocialField
        /// object with the passed key.
        /// </summary>
        /// <param name="key">Field name
        /// </param>
        /// <param name="value">OpenSocialField object to associate with key
        /// </param>
        public void setField(string key, OpenSocialField value)
        {
            fields[key] = value;
        }

        public void setValue(string key, object value)
        {
            var field = new OpenSocialField(false);
            field.addValue(value);
            setField(key, field);
        }

        /// <summary> Returns the names of all properties associated with the instance as an
        /// array of Java String objects.
        /// </summary>
        public string[] fieldNames()
        {
            var fieldNames = new System.Collections.Generic.List<string>();

            foreach (var field in fields)
                fieldNames.Add(field.Key);

            return fieldNames.ToArray();
        }


        public System.Collections.Generic.IDictionary<string, string> getData()
        {
            var data = new System.Collections.Generic.Dictionary<string, string>();

            foreach (var field in fields)
                if (!field.Value.IsComplex && !string.IsNullOrEmpty(field.Value.StringValue)) // TODO: What if IsComplex?
                    data.Add(field.Key, field.Value.StringValue);

            return data;
        }

        public override string ToString()
        {
            var allFields = new System.Text.StringBuilder();
            allFields.AppendLine(GetType().Name + ":");
            foreach (var entry in fields)
            {
                OpenSocialField value = entry.Value;
                string valueString = value.IsComplex
                    ? ToFriendlyString(value.getStringValues())
                    : value.StringValue;
                allFields.AppendLine(entry.Key + ": " + valueString);
            }
            return allFields.ToString();
        }

        private static string ToFriendlyString(System.Collections.Generic.IList<string> list)
        {
            var sb = new System.Text.StringBuilder();
            sb.Append(list.Count + " value(s)");
            foreach (var entity in list)
            {
                var s = entity.Replace("\r", "").Replace("\n", ", ");
                if (s.Length > 2 && s[s.Length - 2] == ',')
                    s = s.Remove(s.Length - 2, 1);
                sb.Append( ", { " + s + "}");
            }

            return sb.ToString();
        }
    }
}